﻿/* ######################################## *\
 * ### Copyright (C) 2009 AJ Ravindiran ### *
 * ### HTTP://THEAJ.NET/ <AJ@THEAJ.NET> ### *
\* ######################################## */
using System;

namespace AJRavindiran.Jolt.RuneScape.Network.Codecs
{
    /// <summary>
    /// Represents a Encoder which provides writing methods to a client.
    /// </summary>
    public class Encoder
    {
        #region Fields
        private int mWriteCapacity;
        private int mOutputOffset = 0;
        private int mBitPosition = 0;
        private byte[] mOutputBuffer;
        private int mFrameStackPtr = -1;
        private static int mFrameStackSize = 10;
        private static int[] mBitMaskOut = new int[32];
        private int[] mFrameStack = new int[mFrameStackSize];
        #endregion

        #region Properties
        public byte[] OutputBuffer
        {
            get { return mOutputBuffer; }
            set { mOutputBuffer = value; }
        }

        public int OutputOffset
        {
            get { return mOutputOffset; }
            set { mOutputOffset = value; }
        }
        #endregion

        #region Constructors
        public Encoder(int outCapacity)
        {
            this.mWriteCapacity = outCapacity;
            this.mOutputBuffer = new byte[outCapacity];

            for (int i = 0; i < 32; i++)
            {
                mBitMaskOut[i] = (1 << i) - 1;
            }
        }
        #endregion

        #region Methods
        public void WriteInt_v1(int i)
        {
            WriteByte(i << 8);
            WriteByte(i);
        }

        public void ExpandOutBuffer()
        {
            byte[] oldBuffer = mOutputBuffer;
            mOutputBuffer = new byte[oldBuffer.Length + 1000];
            System.Array.Copy(oldBuffer, 0, mOutputBuffer, 0, oldBuffer.Length);
        }

        public void WriteByte(int i)
        {
            if (mOutputOffset >= mOutputBuffer.Length)
            {
                ExpandOutBuffer();
            }
            mOutputBuffer[mOutputOffset++] = (byte)i;
        }

        public void WriteByte(int i, int position)
        {
            if (position >= mOutputBuffer.Length)
            {
                ExpandOutBuffer();
            }
            mOutputBuffer[position] = (byte)i;
        }

        public void WriteByteA(int i)
        {
            WriteByte(i + 128);
        }

        public void WriteByteS(int i)
        {
            WriteByte(128 - i);
        }

        public void WriteByteC(int i)
        {
            WriteByte(-i);
        }

        public void WriteBytes(byte[] abyte0, int i, int j)
        {
            for (int k = j; k < j + i; k++)
                WriteByte(abyte0[k]);
        }

        public void WriteBytesS(byte[] abyte0, int i, int j)
        {
            for (int k = j; k < j + i; k++)
                WriteByte(-128 + abyte0[k]);
        }

        public void WriteBytesA(byte[] abyte0, int i, int j)
        {
            for (int k = j; k < j + i; k++)
                WriteByte(abyte0[k] - 128);
        }

        public void WriteBytesC(byte[] abyte0, int i, int j)
        {
            for (int k = j; k < j + i; k++)
                WriteByte(abyte0[k] + 128);
        }

        public void WriteBytes_reverse(byte[] abyte0, int i, int j)
        {
            for (int k = (j + i) - 1; k >= j; k--)
            {
                WriteByte(abyte0[k]);
            }
        }

        public void WriteBytes_reverseA(byte[] abyte0, int i, int j)
        {
            for (int k = (j + i) - 1; k >= j; k--)
            {
                WriteByteA(abyte0[k]);
            }
        }

        public void WriteWordBigEndianA(int i)
        {
            WriteByte(i + 128);
            WriteByte(i >> 8);
        }

        public void WriteWordA(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i + 128);
        }

        public void WriteDWord_v1(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i);
            WriteByte(i >> 24);
            WriteByte(i >> 16);
        }

        public void WriteDWord_v2(int i)
        {
            WriteByte(i >> 16);
            WriteByte(i >> 24);
            WriteByte(i);
            WriteByte(i >> 8);
        }

        public void WriteVarSizeByte(int i)
        {
            if ((i & 255) >= 128)
            {
                WriteWord(i - 32768);
            }
            else
            {
                WriteByte(i);
            }
        }

        public void WriteWord(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i);
        }

        public void WriteWordBigEndian(int i)
        {
            WriteByte(i);
            WriteByte(i >> 8);
        }

        public void Write3Byte(int i)
        {
            WriteByte(i >> 16);
            WriteByte(i >> 8);
            WriteByte(i);
        }

        public void WriteDWord(int i)
        {
            WriteByte(i >> 24);
            WriteByte(i >> 16);
            WriteByte(i >> 8);
            WriteByte(i);
        }

        public void WriteDWordBigEndian(int i)
        {
            WriteByte(i);
            WriteByte(i >> 8);
            WriteByte(i >> 16);
            WriteByte(i >> 24);
        }

        public void WriteQWord(long l)
        {
            WriteByte((int)(l >> 56));
            WriteByte((int)(l >> 48));
            WriteByte((int)(l >> 40));
            WriteByte((int)(l >> 32));
            WriteByte((int)(l >> 24));
            WriteByte((int)(l >> 16));
            WriteByte((int)(l >> 8));
            WriteByte((int)l);
        }

        public static byte[] StrToByteArray(string str)
        {
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            return encoding.GetBytes(str);
        }

        public void WriteString(String s)
        {
            byte[] stringBytes = StrToByteArray(s);

            for (int i = 0; i < s.Length; i++)
                WriteByte(stringBytes[i]);
            WriteByte(0);
        }

        public void CreateFrame(int id)
        {
            WriteByte(id);
        }

        public void CreateFrameVarSize(int id)
        {
            WriteByte(id);
            WriteByte(0);
            if (mFrameStackPtr >= mFrameStackSize - 1)
            {
                throw new Exception("RSOutputStream[CreateFrameVarSize] - Stack overflow");
            }
            else
            {
                mFrameStack[++mFrameStackPtr] = mOutputOffset;
            }
        }

        public void CreateFrameVarSizeWord(int id)
        {
            WriteByte(id);
            WriteWord(0);
            if (mFrameStackPtr >= mFrameStackSize - 1)
            {
                JoltEnvironment.GetLogger().WriteWarn("RSOutputStream[CreateFrameVarSizeWord] - Stack overflow");
            }
            else
            {
                mFrameStack[++mFrameStackPtr] = mOutputOffset;
            }
        }

        public void EndFrameVarSize()
        {
            if (mFrameStackPtr < 0)
            {
                JoltEnvironment.GetLogger().WriteWarn("RSOutputStream[EndFrameVarSize] - Stack empty");
            }
            else
            {
                WriteFrameSize(mOutputOffset - mFrameStack[mFrameStackPtr--]);
            }
        }

        public void EndFrameVarSizeWord()
        {
            if (mFrameStackPtr < 0)
            {
                JoltEnvironment.GetLogger().WriteWarn("RSOutputStream[EndFrameVarSizeWord] - Stack empty");
            }
            else
            {
                WriteFrameSizeWord(mOutputOffset - mFrameStack[mFrameStackPtr--]);
            }
        }

        public void WriteFrameSize(int i)
        {
            WriteByte(i, (mOutputOffset - i - 1));
        }

        public void WriteFrameSizeWord(int i)
        {
            WriteByte((i >> 8), (mOutputOffset - i - 2));
            WriteByte(i, (mOutputOffset - i - 1));
        }

        public void InitBitAccess()
        {
            mBitPosition = mOutputOffset * 8;
        }

        public void FinishBitAccess()
        {
            mOutputOffset = (mBitPosition + 7) / 8;
        }

        public void AddBit(int bit, int pos)
        {
            if (pos >= mOutputBuffer.Length)
            {
                ExpandOutBuffer();
            }
            mOutputBuffer[pos] &= (byte)~bit;
        }

        public void PlaceBit(int bit, int pos)
        {
            if (pos >= mOutputBuffer.Length)
            {
                ExpandOutBuffer();
            }
            mOutputBuffer[pos] |= (byte)bit;
        }

        public void WriteBits(int numBits, int value)
        {
            int bytePos = mBitPosition >> 3;
            int bitOffset = 8 - (mBitPosition & 7);
            mBitPosition += numBits;
            for (; numBits > bitOffset; bitOffset = 8)
            {
                AddBit(mBitMaskOut[bitOffset], bytePos);
                PlaceBit(((value >> (numBits - bitOffset)) &
                    mBitMaskOut[bitOffset]), bytePos++);
                numBits -= bitOffset;
            }
            if (numBits == bitOffset)
            {
                AddBit(mBitMaskOut[bitOffset], bytePos);
                PlaceBit((value & mBitMaskOut[bitOffset]), bytePos);
            }
            else
            {
                AddBit((mBitMaskOut[numBits] <<
                    (bitOffset - numBits)), bytePos);
                PlaceBit((value & mBitMaskOut[numBits]) <<
                    (bitOffset - numBits), bytePos);
            }
        }
        #endregion
    }
}
